This page last changed on Dec 01, 2006 by mmccorma.
This page is under review for accuracy
Message Acknowledgements and Delivery Modes
When implementing the JMS client it became apparent that the JMS specification offered a considerable degree of latitude for interpreting the precise semantics of acknowledgement modes and it also did not cover all acknowledgement modes that are of interest.
Here we describe the precise semantics of the JMS acknowledgement modes and the additional modes that the JMS client provides.
In this discussion, "the client" refers to the JMS client implementation and "the user" refers to code that is part of the client application (i.e. code written by the end-user developer).
AUTO_ACKNOWLEDGE (JMS)
In this mode, the client acknowledges each message once it has been received by the user. In the case of an asynchronous message consumer, this means that an acknowledgement is sent once the onMessage method of a message listener has completed without throwing an exception of any sort. For a synchronous consumer, it means when the receive() method has returned the message to the user.
In this mode, each HANDLE NOTIFY sent from the server to the client will result in one CHANNEL ACK being sent from the client to the server.
CLIENT_ACKNOWLEDGE (JMS)
In this mode, the user acknowledges messages manually by calling the acknowledge() method. The user does not have to acknowledge each message therefore the user can decide on a batching strategy in order to reduce network traffic.
The JMS does not say how many message a client is allowed to receive before acknowledging. However, it does talk in vague terms about implementations making sure clients don't go too long without acknowledging to avoid resource exhaustion.
Qpid introduces a "prefetch" which specifies, for a particular destination, how many messages the client can read without sending an acknowldgement. The method Session.setDefaultPrefetch(int) allows a default value to be configured at the session level and an overloaded createConsumer() method exists allowing it to be specified at the point of consumer construction.
Calling Message.acknowledge() sends a CHANNEL ACK to be sent from the client to the server. This acknowledges the receipt of all messages up to and including this one.
DUPS_OK_ACKNOWLEDGE (JMS)
In this mode, the client acknowledges message receipt as in the case of AUTO_ACKNOWLEDGE but is not obliged to acknowledge each message immediately upon successful completion of the onMessage() or receive() methods.
This means that the client can have an "acknowledgement strategy" that can provide higher performance at the expense of potential redelivery of messages in the event of a failure.
Qpid provides a default configurable strategy parameterised on number of messages received and elapsed time since last acknowledgement. The user can configure the client to send acknowledgements every n messages (where n <= the prefetch value for the consumer) or every k milliseconds whichever is reached sooner. Values of zero for either n or k means that component of the trigger is disabled. Setting n = 0 and k = 0 results in behaviour identical to CLIENT_ACKNOWLEDGE. Setting n = 1 and k = 0 results in behaviour identical to AUTO_ACKNOWLEDGE.
PRE_ACKNOWLEDGE (non-JMS)
A mode not covered by the JMS specification is one where the client acknowledges a message before calling the onMessage() or receive() methods. This method results in the server receiving one CHANNEL ACK per HANDLE NOTIFY as in AUTO_ACKNOWLEDGE but the difference is that the ack will/can(? - this choice could cause a 'DUP' effect) be sent even if user code can throws an error.
This is useful where the user does not want to trigger redelivery of a message if user code fails or where synchronous operation is used and it is expected that there is some delay before receive() is called (which would in turn cause the server to have to wait some time before receiving the ack).
The constant Session.PRE_ACKNOWLEDGE defines this mode.
NO_ACKNOWLEDGE (non-JMS)
Certain data may be time sensitive in the sense that redelivery is pointless - if the client cannot process it at the instant it is sent there is no point in redelivering it.
In this case, acks are redundant. Since TCP means that the server can be sure the client received the message the only problem could be client error.
Setting NO_ACKNOWLEDGE means that the client never needs to send CHANNEL_ACK messages. The constant Session.NO_ACKNOWLEDGE defines this mode.
Delivery Modes
For message production, similar considerations apply. JMS defines two delivery modes, PERSISTENT and NON_PERSISTENT which allow the implmentor considerable freedom of implementation.
Unfortunately the JMS specification addresses what are really two separate reliability concerns with a single delivery mode. OpenAMQ adds an additional mode to give the user the ability to have finer control over the tradeoff between performance and reliability.
The default delivery mode can be set on a producer. This can be overridden on each message sent.
PERSISTENT (JMS)
Persistent is the straightforward option. Messages are sent with the persistent flag set to true which means that they will be committed to stable storage. HANDLE SEND frames are sent with the confirm tag set to true. When the client receives the HANDLE REPLY frame it knows that the message has been committed to stable storage on the broker.
We need to clarify with Pieter what confirm tags really mean. Do they mean pure physical receipt of the message or receipt & completion of all necessary processing? The semantics of 'processing' will vary from message-type to message-type. For example would 'HANDLE SEND' mean delivery to all end points (clients)... unlikely... or delivery to all the data-structures - queues, topics etc, or just delivery to the mux?
In this particular case 'processing' would probably mean writing to some persistent storage. Whatever the 'rule', it should be unambiguous, understood by all developers and enforced. Perhaps a 'processed' flag could be associated with the tag to indicate that it should only be generated when processing has also completed? Alternatively this could be a field that can optionally piggy-back in the confirm tag indicating that the confirm tag is also acknowledgement of completion of all necessary processing. If it does not contain that flag, then a separate acknowledgement message may be generated some time later. This gives the broker some implementation freedom.
The MessageProducer.send() method blocks until the HANDLE REPLY has been received therefore the user can know that a message has been reliably sent to the broker.
NON_PERSISTENT (JMS)
Non persistent gives maximum performance with least guarantees. The persistent flag is set to false in each message which means that if the broker dies all messages that have no been delivered at that point are lost irretrievably.
Also no acks are requested in the HANDLE SEND. Acks are pointless in this case since after sending the ack the server could die and the message would be lost.
PERSISTENT_GROUPACK (non-JMS)
An ack means that the client knows that the last message it sent was received successfully. However, acks have a considerable performance overhead. A client may want to be able to send message quickly but also have the guarantee that if the broker dies the messages will not be lost.
PERSISTENT_GROUPACK means that messages are written to stable storage, which incurs an overhead, but the client only requests an ack every n messages. The Session.setGroupAckThreshold() method allows the client to specify the ack frequency.
Note that this is different to a transacted commit since in this mode, messages can be delivered to client before the group-ack. Transacted messages are not available to clients until the commit.
TODO: define some API to allow the client to determine the set of mesages not acked in the event of failure.
|